package br.com.comanda.dao.autosql;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import android.util.Log;
import br.com.comanda.dao.autosql.annotations.Column;
import br.com.comanda.dao.autosql.annotations.ForeignKey;
import br.com.comanda.dao.autosql.annotations.Id;
import br.com.comanda.dao.autosql.annotations.Table;
import br.com.comanda.dao.autosql.dto.DataTransferObject;
import br.com.comanda.dao.autosql.dto.Dominio;
import br.com.comanda.dao.autosql.dto.join.JoinBetweenMiddleTable;
import br.com.comanda.dao.autosql.dto.join.JoinBetweenTwoTables;
import br.com.comanda.dao.autosql.exception.TableNotNamedException;
import br.com.comanda.util.AnnotationUtils;
import br.com.comanda.util.ArrayUtils;
import br.com.comanda.util.CollectionUtils;
import br.com.comanda.util.Constantes;
import br.com.comanda.util.StringUtils;

/**
 * Realiza montagem de SQL nativo
 * 
 * @author thiago
 * @since 09/01/2013
 */
public class SQLBuilder {

	/**
	 * Cria um apelido para uma tabela
	 */
	public static String aliasTo(Class<?> clazz) {
		return clazz.getAnnotation(Table.class).name().substring(0, 3);
	}

	/**
	 * Retorna o nome da tabela no banco de dados que o objeto E mapeia
	 */
	public static String tableName(Class<?> clazz) {
		Table table = (Table) clazz.getAnnotation(Table.class);

		if (table == null || !StringUtils.hasLength(table.name())) {
			throw new TableNotNamedException(String.format(
					"A entidade %s deve ter um nome. Anote-a com @Table",
					clazz.getName()));
		} else {
			return table.name();
		}
	}

	/**
	 * Retorna os campos para realizar o select. Se for busca simples, a coluna
	 * deve ter o metadado simples na anotação coluna.
	 * 
	 * O ID sempre é buscado na busca simples.
	 */
	public static String[] getSelectFields(Class<?> clazz, boolean simpleSearch) {
		List<String> list = null;
		Field[] fields = null;

		try {
			fields = clazz.getDeclaredFields();

			if (!ArrayUtils.isEmpty(fields)) {
				list = new ArrayList<String>();

				for (Field f : fields) {
					if (!f.isAnnotationPresent(Column.class)) {
						continue;
					} else {
						Column column = f.getAnnotation(Column.class);
						if (column.selectable()) {
							if ((simpleSearch && column.simple())
									|| !simpleSearch
									|| f.isAnnotationPresent(Id.class)) {
								list.add(column.name());
							}
						}
					}
				}
			}

			return ArrayUtils.toStringArray(list);
		} catch (Exception e) {
			Log.e(Constantes.TAG_LOG, "getSelectFields", e);
			return new String[0];
		}
	}

	/**
	 * Utilizado somente para pegar campos de um DTO que forem de uma classe
	 * requerida
	 */
	public static String getSelectFields(
			Class<? extends DataTransferObject> clazz) {
		StringBuilder select = new StringBuilder();

		for (Field f : clazz.getDeclaredFields()) {
			if (f.isAnnotationPresent(Dominio.class)) {
				Dominio domain = f.getAnnotation(Dominio.class);
				select.append(String.format("%s.%s, ",
						SQLBuilder.aliasTo(domain.tabela()), domain.coluna()));

				if (domain.alias().length() > 0) {
					select.delete(select.length() - 2, select.length());
					select.append(String.format(" AS %s, ", domain.alias()));
				}
			}
		}

		return StringUtils.hasLength(select.toString()) ? select.delete(
				select.length() - 2, select.length()).toString() : "1";
	}

	/**
	 * Monta a clausula join de duas tabelas
	 */
	public static String joinClause(Class<? extends DataTransferObject> clazz,
			String alias, String aliasJoinClazz) {
		if (clazz.isAnnotationPresent(JoinBetweenTwoTables.class)) {
			JoinBetweenTwoTables join = clazz
					.getAnnotation(JoinBetweenTwoTables.class);
			return String.format("%s.%s = %s.%s", alias,
					join.columnInnerTable(), aliasJoinClazz,
					join.columnOuterTable());
		} else if (clazz.isAnnotationPresent(JoinBetweenMiddleTable.class)) {
			JoinBetweenMiddleTable join = clazz
					.getAnnotation(JoinBetweenMiddleTable.class);
			Class<?> middleTableClass = join.middleTableName();

			List<Annotation> foreignKeys = AnnotationUtils
					.getForeignKeyFieldAnnotation(middleTableClass);

			if (CollectionUtils.isEmpty(foreignKeys) || foreignKeys.size() < 2) {
				throw new IllegalArgumentException(
						"A tabela do meio de join deve possuir duas anotações @ForeignKey");
			} else {
				ForeignKey foreignKeyInner = (ForeignKey) foreignKeys.get(0);
				ForeignKey foreignKeyOuter = (ForeignKey) foreignKeys.get(1);

				return String.format("%s.%s = %s.%s", foreignKeyInner
						.tableReference().substring(0, 3), foreignKeyInner
						.columnReference(), foreignKeyOuter.tableReference()
						.substring(0, 3), foreignKeyOuter.columnReference());
			}
		} else {
			throw new IllegalArgumentException(
					"O tipo de join usado nao é suportado. Favor incluir "
							+ "nos métodos SQLBuilder.joinClause e AbstractDTORepository.buildSQL a lógica para "
							+ "suporte a esse novo tipo de join");
		}
	}
}
